//
//  NetworkingSession.swift
//  XileLife
//
//  Created by xiaoyuan on 2020/4/2.
//  Copyright © 2020 寻宝天行. All rights reserved.
//

import Foundation
import Moya
import Alamofire
import ObjectMapper
import HandyJSON

public typealias HTTPMethod = Moya.Method
public typealias ParameterEncoding = Moya.ParameterEncoding
public typealias JSONEncoding = Moya.JSONEncoding
public typealias URLEncoding = Alamofire.URLEncoding
public typealias MyValidationType = Moya.ValidationType
public typealias MySampleResponse = Moya.EndpointSampleResponse
public typealias MyStubBehavior = Moya.StubBehavior
public typealias CompletionHandler<R> = (Result<R, Error>) -> Void where R: Responseable
public typealias Cancellable = Moya.Cancellable
public typealias Task = Moya.Task

public protocol MyApiTargetType: TargetType {
    var parameters: [String: Any] { get }
    var parameterEncoding: ParameterEncoding { get }
    var stubBehavior: MyStubBehavior { get }
    var sampleResponse: MySampleResponse { get }
    var method: HTTPMethod { get }
}

public extension MyApiTargetType {
    var isShowLoading: Bool { return false }
    
    var task: Task {
        return .requestParameters(parameters: parameters, encoding: parameterEncoding)
    }
    
    var parameterEncoding: ParameterEncoding {
        return URLEncoding.default
    }
    
    var method: HTTPMethod {
        return .post
    }
    
    var validationType: MyValidationType {
        return .successCodes
    }
    
    var stubBehavior: StubBehavior {
        return .never
    }
    
    var sampleData: Data {
        return "mock 数据，可添加你的本地数据自测".data(using: String.Encoding.utf8)!
    }
    
    var sampleResponse: MySampleResponse {
        return .networkResponse(200, self.sampleData)
    }
}


public struct NetworkingSession<T: MyApiTargetType> {
    public let provider: MoyaProvider<T>
    public init(provider: MoyaProvider<T> = defaultProvider()) {
        self.provider = provider
    }
    
    
    /// 此方法用于已明确网络请求的`response`，无需解析`response.data`，可在回调中获取 `response.rawData`拿到json数据
    ///
    /// 调用这个方法发送网络请求需要传入一个实现MyApiTargetType的实例
    ///
    ///     let session = NetworkingSession<Api>()
    ///     session.request(.promotionTimes,  callbackQueue: nil, progress: nil, success: { (result) in
    ///         ASDebugLog(result.rawData)
    ///     }) { (error) in
    ///         ASDebugLog(error)
    ///     }
    ///
    @discardableResult
    public func request(_ target: T,
                        callbackQueue: DispatchQueue? = DispatchQueue.main,
                        progress: ProgressBlock? = nil,
                        completion: @escaping CompletionHandler<ResponseModel<NormalModel>>) -> Cancellable {
        request(target,
                responseType: ResponseModel<NormalModel>.self,
                callbackQueue: callbackQueue,
                progress: progress,
                completion: completion)
    }
    
    /// 此方法用于已明确网络请求的response，但是不确定response.data的解决方案
    ///
    /// 调用这个方法发送网络请求需要传入一个实现MyApiTargetType的实例，且需要明确后台返回的model类型
    ///
    ///     let session = NetworkingSession<Api>()
    ///     session.request(.promotionTimes, model: TimeHandyJSONModel.self, success: { (result) in
    ///         ASDebugLog(result)
    ///     }) { (error) in
    ///         ASDebugLog(error)
    ///     }
    ///
    @discardableResult
    public func requestModel<M: HandyJSON>(_ target: T,
                                           model: M.Type,
                                           callbackQueue: DispatchQueue? = DispatchQueue.main,
                                           progress: ProgressBlock? = nil,
                                           completion: @escaping (Result<M, Error>) -> Void) -> Cancellable {
        request(target,
                responseType: ResponseModel<M>.self,
                callbackQueue: callbackQueue,
                progress: progress) { result in
            switch result {
            case let .success(response):
                if let dataModel = response.dataModel {
                    completion(.success(dataModel))
                }
                else {
                    completion(.failure(ApiError.internal(code: response.code, message: response.msg)))
                }
            case let .failure(error):
                completion(.failure(error))
            }
        }
    }
    
    @discardableResult
    public func requestArray<M: HandyJSON>(_ target: T,
                                           model: M.Type,
                                           callbackQueue: DispatchQueue? = DispatchQueue.main,
                                           progress: ProgressBlock? = nil,
                                           completion: @escaping (Result<[M], Error>) -> Void) -> Cancellable {
        request(target,
                responseType: ResponseModel<M>.self,
                callbackQueue: callbackQueue,
                progress: progress) { result in
            switch result {
            case let .success(response):
                if response.isSuccess {
                    completion(.success(response.data ?? []))
                }
                else {
                    completion(.failure(ApiError.internal(code: response.code, message: response.msg)))
                }
            case let .failure(error):
                completion(.failure(error))
            }
        }
    }
    
    /// 此方法用于已明确网络请求的response，但是不确定response.data的解决方案
    ///
    /// 调用这个方法发送网络请求需要传入一个实现MyApiTargetType的实例，且需要明确后台返回的model类型
    ///
    ///     let session = NetworkingSession<Api>()
    ///     session.request(.promotionTimes, model: TimeHandyJSONModel.self, success: { (result) in
    ///         ASDebugLog(result)
    ///     }) { (error) in
    ///         ASDebugLog(error)
    ///     }
    ///
    @discardableResult
    public func request<M: HandyJSON>(_ target: T,
                                      model: M.Type,
                                      callbackQueue: DispatchQueue? = DispatchQueue.main,
                                      progress: ProgressBlock? = nil,
                                      completion: @escaping CompletionHandler<ResponseModel<M>>) -> Cancellable {
        request(target,
                responseType: ResponseModel<M>.self,
                callbackQueue: callbackQueue,
                progress: progress,
                completion: completion)
    }
    
    /// 此方法用于不明确网络请求的response，需要自定义一个实现Responseable协议的response解决方案
    ///
    /// 调用这个方法发送网络请求需要传入一个实现MyApiTargetType的实例，且需要明确后台返回的response类型
    ///
    ///     let session = NetworkingSession<Api>()
    ///     session.request(.promotionTimes, responseType: CustomResponse.self, success: { (result) in
    ///         if result.isSuccess == true {
    ///             ASDebugLog(result)
    ///         }
    ///     }) { (error) in
    ///         ASDebugLog(error)
    ///     }
    ///
    @discardableResult
    public func request<R: Responseable>(_ target: T,
                                         responseType: R.Type,
                                         callbackQueue: DispatchQueue? = DispatchQueue.main,
                                         progress: ProgressBlock? = nil,
                                         completion: @escaping CompletionHandler<R>) -> Cancellable {
        self.provider.request(target, callbackQueue: callbackQueue, progress: { (progressResponse) in
            if let progress = progress {
                progress(ProgressResponse(progress: progressResponse.progressObject))
            }
        }) { (result) in
            switch result {
            case let .success(response):
                handleResponse(response: response, completion: completion)
            case let .failure(error):
                completion(.failure(ApiError.service(error: error)))
            }
        }
    }
}

extension NetworkingSession {
    // 处理请求成功数据
    fileprivate func handleResponse<R: Responseable>(response: Moya.Response, completion: CompletionHandler<R>){
        
        var baseModel: R?
        if response.data.count == 0 {
            baseModel = try? R.map(json: ["code": 200])
        }
        else {
            let jsonObj = try? response.mapJSON()
            if  let jsonObj = jsonObj as? [String: Any] {
                baseModel = try? R.map(json: jsonObj)
            }
        }
        if let baseModel = baseModel {
            completion(.success(baseModel))
        }
        else {
            completion(.failure(ApiError.service(error: MoyaError.jsonMapping(response))))
        }
        
    }
}

extension NetworkingSession {
    public static func defaultProvider() -> MoyaProvider<T> {
        var _plugins = plugins
        if let block = NetworkingConfig.shared.plugins {
            let array = block()
            _plugins.append(contentsOf: array)
        }
        return newProvider(plugins: _plugins)
    }
    
    static func endpointsClosure<T>() -> (T) -> Endpoint where T: MyApiTargetType {
        return { target in
            
            // 全局请求头中添加token
            var header: [String: String] = [:]
            
            if let closure = NetworkingConfig.shared.headersClosure {
                header = closure()
//                defaultEndpoint = defaultEndpoint.adding(newHTTPHeaderFields: closure())
            }
            var defaultEndpoint = Endpoint(
                url: URL(target: target).absoluteString,
                sampleResponseClosure: { target.sampleResponse },
                method: target.method,
                task: target.task,
                httpHeaderFields: header
            )
            defaultEndpoint = defaultEndpoint.adding(newHTTPHeaderFields: target.headers ?? [:] )
            return defaultEndpoint
        }
    }
    static func endpointResolver() -> MoyaProvider<T>.RequestClosure {
        return { (endpoint, closure) in
            do {
                var request = try endpoint.urlRequest()
                request.httpShouldHandleCookies = false
                request.timeoutInterval = 30
                closure(.success(request))
            } catch let error {
                closure(.failure(MoyaError.underlying(error, nil)))
            }
        }
    }
    
    static func apiKeysBasedStubBehaviour<T>(_ target: T) -> Moya.StubBehavior where T: MyApiTargetType {
        return target.stubBehavior;
    }
    
    static var plugins: [PluginType] {
        let activityPlugin = NewNetworkActivityPlugin { (state, targetType) in
            switch state {
            case .began:
                if targetType.isShowLoading { //这是我扩展的协议
                    // 显示loading
                }
            case .ended:
                if targetType.isShowLoading { //这是我扩展的协议
                    // 关闭loading
                }
            }
        }
        
        var items: [PluginType] = [
            activityPlugin
        ]
        #if DEBUG
        if NetworkingConfig.shared.isEnableLogging {
            items.append(myLoggorPlugin)
        }
        #endif
        items.append(myApiTargetTypePlugin)
        return items
    }
    
}


func newProvider<T>(plugins: [PluginType], session: Session = newManager()) -> MoyaProvider<T> where T: MyApiTargetType {
    return MoyaProvider(endpointClosure: NetworkingSession<T>.endpointsClosure(),
                        requestClosure: NetworkingSession<T>.endpointResolver(),
                        stubClosure: NetworkingSession<T>.apiKeysBasedStubBehaviour,
                        session: session,
                        plugins: plugins)
}

func newManager(delegate: Alamofire.SessionDelegate = SessionDelegate(),
                serverTrustManager: ServerTrustManager? = APIServerTrustManager()) -> Session {
    
    let configuration = URLSessionConfiguration.default
    configuration.timeoutIntervalForRequest = 30
    let session = Session.init(configuration: configuration, delegate: SessionDelegate.init(), startRequestsImmediately: false, serverTrustManager: serverTrustManager)
    return session
}

final class APIServerTrustManager: ServerTrustManager {
    init() {
        let allHostsMustBeEvaluated = false
        let evaluators = ["": DisabledTrustEvaluator()]
        super.init(allHostsMustBeEvaluated: allHostsMustBeEvaluated, evaluators: evaluators)
    }
    
    override func serverTrustEvaluator(forHost host: String) throws -> ServerTrustEvaluating? {
        // 允许所有https 的证书，解决通过代理请求https api 被拒绝的问题
        return self.evaluators[""]
    }
}

public protocol Responseable {
    var isSuccess: Bool { get }
    static func map(json: [String: Any]) throws -> Self?
}

extension Responseable {
    public static func map(json: [String: Any]) throws -> Self? {
        return nil
    }
}

public struct ResponseModel<M>: Mappable {
    public typealias ModelType = M
    
    public init?(map: Map) {}
    
    public var code: Int?
    public var codeStr: String?
    public var status: String?
    
    public var msg: String?
    
    public var data: [ModelType]?
    public var dataStr: String?
    public var dataModel: ModelType?
    public var rawData: Any?
    
    public var isSuccess: Bool {
        if let status = status, status.uppercased() == "OK" {
            return true
        }
        let code = code ?? Int(codeStr ?? "") ?? -1
        return NetworkingConfig.shared.isSuccessCode(code)
    }
    
    public static func map(json: [String: Any]) -> Self?  {
        let model = ResponseModel<ModelType>.init(JSON: json)
        return model
    }

    mutating public func mapping(map: Map) {
        code <- map["code"]
        data <- map["data"]
        
        msg <- map["msg"]
        msg <- map["message"]
        
        codeStr <- map["code"]
        dataStr <- map["data"]
        dataModel <- map["data"]
        rawData <- map["data"]
        dataModel <- map["data.data"]
        data <- map["data.data"]
        rawData <- map["data.data"]
        data <- map["result.data"]
        dataModel <- map["result.data"]
        rawData <- map["result.data"]
        status <- map["status"]
    }
}

extension ResponseModel: Responseable where ModelType: HandyJSON {
    public static func map(json: [String: Any]) throws -> Self?  {
        var model = ResponseModel<ModelType>.init(JSON: json)
        if model?.dataModel == nil {
            if let dataDict = model?.rawData as? [String: Any] {
                model?.dataModel = JSONDeserializer<ModelType>.deserializeFrom(dict: dataDict)
            }
        }
        if model?.data == nil {
            if let dataArray = model?.rawData as? [[String: Any]] {
                model?.data = JSONDeserializer<ModelType>.deserializeModelArrayFrom(array: dataArray) as? [ModelType]
            }
        }
        return model
    }
}

extension ResponseModel where ModelType: Mappable {
    public static func map(json: [String: Any]) throws -> Self?  {
        var model = ResponseModel<ModelType>.init(JSON: json)
        if model?.dataModel == nil {
            if let dataDict = model?.rawData as? [String: Any] {
                model?.dataModel =  Mapper<M>().map(JSONObject: dataDict)
            }
        }
        if model?.data == nil {
            if let dataArray = model?.rawData as? [[String: Any]] {
                model?.data = Mapper<M>().mapArray(JSONArray: dataArray)
            }
        }
        return model
    }
}

extension ResponseModel where ModelType: Decodable {
    public static func map(json: [String: Any]) throws -> Self?  {
        var model = ResponseModel<ModelType>.init(JSON: json)
        if model?.dataModel == nil {
            if let dataDict = model?.rawData as? [String: Any] {
                let data = try JSONSerialization.data(withJSONObject: dataDict, options: .fragmentsAllowed)
                let decoder = JSONDecoder()
                let object = try decoder.decode(ModelType.self, from: data)
                model?.dataModel = object
            }
        }
        if model?.data == nil {
            if let dataArray = model?.rawData as? [[String: Any]] {
                let data = try JSONSerialization.data(withJSONObject: dataArray, options: .fragmentsAllowed)
                let decoder = JSONDecoder()
                let object = try decoder.decode([ModelType].self, from: data)
                model?.data = object
            }
        }
        return model
    }
}

public struct MyApiTargetTypePlugin: PluginType {
    
    /**
     Initialize a new `MyApiTargetTypePlugin`.
     
     - parameters:
     - tokenClosure: A closure returning the token to be applied in the pattern `Authorization: <AuthorizationType> <token>`
     */
    public init() {}
    
    /**
     Prepare a request by adding an authorization header if necessary.
     
     - parameters:
     - request: The request to modify.
     - target: The target of the request.
     - returns: The modified `URLRequest`.
     */
    public func prepare(_ request: URLRequest, target: TargetType) -> URLRequest {
        
        let request = request
        //        if var urlStr = request.url?.absoluteString {
        //
        //            if urlStr.range(of: "&") == nil, urlStr.last != "?" {
        //                urlStr += "?"
        //            }
        //            else if urlStr.range(of: "&") != nil, urlStr.last != "?" {
        //                urlStr += "&"
        //            }
        //            urlStr += "iu=\(Self.getDateString())"
        //            request.url = URL(string: urlStr)
        //        }
        return request
    }
    
    private static func getDateString() -> String {
        let date = Date(timeIntervalSinceNow: 0) //获取当前时间0秒后的时间
        let time = date.timeIntervalSince1970 * 1000 // *1000 是精确到毫秒，不乘就是精确到秒
        let timeString = String(format: "%.0f", time)
        return timeString
    }
}
private let myApiTargetTypePlugin = MyApiTargetTypePlugin()

/// 配置网络请求的全局
final public class NetworkingConfig {
    public static let shared = NetworkingConfig()
    private init() {}
    fileprivate var headersClosure: (() -> ([String: String]))?
    fileprivate var successCodesClosure: (() -> ([Int]))?
    fileprivate var ignoreCodesClosure: (() -> ([Int]))?
    fileprivate var plugins: (() -> ([PluginType]))?
    public var isEnableLogging = false
    @discardableResult
    public func headers(_ closure: (() -> ([String: String]))?) -> Self {
        headersClosure = closure
        return self
    }
    @discardableResult
    public func successCodes(_ closure: (() -> ([Int]))?) -> Self {
        successCodesClosure = closure
        return self
    }
    
    @discardableResult
    public func ignoreCodes(_ closure: (() -> ([Int]))?) -> Self {
        ignoreCodesClosure = closure
        return self
    }
    
    @discardableResult
    public func plugins(_ closure: (() -> ([PluginType]))?) -> Self {
        plugins = closure
        return self
    }
    
    fileprivate func isSuccessCode(_ code: Int) -> Bool {
        if let closure = NetworkingConfig.shared.successCodesClosure {
            var codes = closure()
            if let closure = NetworkingConfig.shared.ignoreCodesClosure {
                codes.append(contentsOf: closure())
            }
            return codes.contains(code)
        }
        return code >= 200 && code < 300
    }
    
    public static func isSuccessCode(_ code: Int, in successCodes: [Int] = [200]) -> Bool {
        var _codes = successCodes
        if let closure = NetworkingConfig.shared.ignoreCodesClosure {
            _codes.append(contentsOf: closure())
        }
        return _codes.contains(code)
    }
}

public struct NormalModel: Mappable {
    public init?(map: Map) {
    }
    mutating public func mapping(map: Map) {
        
    }
}

extension NormalModel: HandyJSON {
    public init() {}
}
